from adt import graph
from adt.graph import Digraph, DFS
from pattern.collection.basics import OneToOne


class InstantGraphTool(object):

    def __init__(self, nodeset, edge_predicate):
        self.nodeset = nodeset
        self.edge_predicate = edge_predicate

    def __call__(self, g=None):
        nodes = [graph.Digraph.Node(x) for x in self.nodeset]
        for source in nodes:
            for destination in nodes:
                if source is not destination:
                    t = self.edge_predicate(source.label, destination.label)
                    if t:
                        e = source.connect(destination)
                        if not isinstance(t, bool):
                            e.label = t

        # Simplest way to compute a root-set is to take all nodes
        # (not too efficient though - should think smt up)
        if g is None: g = graph.Digraph()
        g.roots = set(nodes)

        return g


class EdgeSetGraphTool(object):
    
    def __init__(self, edge_set):
        self.E = edge_set
        
    def __call__(self, g=None):
        if g is None: g = Digraph()
        nodes = OneToOne().of(Digraph.Node)
        for u, v, lbl in self._triples():
            nodes[u].connect(nodes[v]).label = lbl
            
        return RootSetTool()(nodes.values())
            
    def _triples(self):
        for el in self.E:
            if isinstance(el, tuple):
                if len(el) == 3:
                    yield el
                elif len(el) == 2:
                    yield el + (None,)
                else:
                    raise ValueError, "invalid edge tuple: %r" % el
            elif isinstance(el, Digraph.Edge):
                yield (el.source.label, el.destination.label, el.label)
            else:
                raise TypeError, "edge expected to be tuple or Digraph.Edge, got %s" % type(el).__name__
        

class PathGraphTool(object):
    
    def __init__(self, paths, marker=lambda x:None):
        self.paths = paths
        self.marker = marker
        
    def __call__(self, g=None):
        cloned_nodes = graph.clone.ClonedNodes()
        cloned_edges = graph.clone.ClonedEdges(cloned_nodes)
        roots = []
        for path in self.paths:
            if len(path) == 0: continue
            roots.append(cloned_nodes[path[0].source])
            for edge in path:
                cloned_edges[edge]
            self.marker(cloned_nodes[path[-1].destination])
        if g is None: g = graph.Digraph()
        g.roots = roots
        return g
    
    
class FlowGraphTool(object):
    """
    A cute builder which employs syntactic sugar by taking advantage of the
    Python interpreter. Start with basic single-node elements generated by
    block(). Then, the >> operator is used for edges, and the | operator is
    used to split flow.
    """
    
    class BuildingBlock(object):
        def __init__(self, node, tag=None):
            self.node = node
            self.tag = tag
    
    def __init__(self, spec=None):
        if spec is None:
            self.incoming = []
            self.outgoing = []
        else:
            nodes, shape = spec
            nodes = [self.block(node) for node in nodes]
            flow = shape(*nodes)
            self.incoming, self.outgoing = flow.incoming, flow.outgoing
    
    @classmethod
    def block(cls, label):
        n = graph.Digraph.Node(label)
        inout = [cls.BuildingBlock(n)] 
        return cls.gen(inout, inout)
    
    @classmethod
    def gen(cls, incoming, outgoing):
        t = FlowGraphTool()
        t.incoming = incoming
        t.outgoing = outgoing
        return t
    
    def __rshift__(self, next): #@ReservedAssignment
        if isinstance(next, FlowGraphTool):
            return self.weld(next)
        else:
            return self.derive(outgoing=[self.BuildingBlock(o.node, next)
                                         for o in self.outgoing])
            
    def __rrshift__(self, prev):
        return self.derive(incoming=[self.BuildingBlock(i.node, prev)
                                     for i in self.incoming])
        
    def __or__(self, other):
        return self.gen(self.incoming + other.incoming, 
                        self.outgoing + other.outgoing)
        
    def __call__(self):
        g = graph.Digraph()
        g.roots = [b.node for b in self.incoming]
        return g
    
    def weld(self, next): #@ReservedAssignment
        for exit in self.outgoing: #@ReservedAssignment
            for entry in next.incoming:
                tag = [exit.tag, entry.tag][exit.tag is None]
                exit.node.connect(entry.node).label = tag
        return self.gen(self.incoming, next.outgoing)
    
    def derive(self, incoming=None, outgoing=None):
        if incoming is None: incoming = self.incoming
        if outgoing is None: outgoing = self.outgoing
        return self.gen(incoming, outgoing)



class RootSetTool(object):
    
    def __call__(self, nodes):
        g = Digraph()
        g.roots = nodes
        origin = {}
        class OriginVisitor(DFS.Visitor):
            def __init__(self, source):
                self.source = source
            def visit_node(self, node):
                origin[node] = self.source
                
        for node in nodes:
            if node not in origin:
                DFS(g, root=node).scan(OriginVisitor(node))
        g.roots = set(origin.itervalues())
        return g
